import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

//存活管理模块
//单例模式,同时会开启一个线程
public class HeartBeatModule implements Runnable {
    //等待的时间
    static long HB_WAIT_TIME = 4000;//在HB_wait状态等待的最大时间
    static long SEND_WAIT_TIME = 1000;//在HB_sur状态等待的最大时间(等待完这个时间后会发送心跳报文)

    //记录存活的客户端的Map
    //参数: String为连接的地址,SurvivalInformation为连接的"存活信息"
    //功能: 仅有存在于此Map的连接是存活的,否则都是认为掉线
    //注意: 此map会被多个线程读写
    public static Map<String, SurvivalInformation> sur_map;
    //[心跳信息]队列(单例)
    //关联: HeartBeatModule模块是这个队列的 "消费者"
    ConcurrentLinkedQueue<HeartBeatMessage> HBqueue = HeartBeatQueue.getInstance().queue;

    static {
        //在类加载时就创建线程
        //只有这个线程可以访问这个单例的所有属性~~
        new Thread(new HeartBeatModule(), "HeartBeatModule").start();
    }

    private HeartBeatModule() {
        sur_map = new ConcurrentHashMap<>();
    }

    @Override
    public void run() {
        System.out.println("HeartBeatModule线程开始运行");
        while (true) {
            //遍历"生存map",进行"计时"
            for (Map.Entry<String, SurvivalInformation> entry : sur_map.entrySet()) {
                long diff_time = System.currentTimeMillis() - entry.getValue().start_time;
                HBstate state = entry.getValue().state;
                CtrlSendQueue send_queue = entry.getValue().send_queue;
                String addr = entry.getKey();

                if (state == HBstate.HB_sur && diff_time > SEND_WAIT_TIME) {
                    //发送心跳报文(把要发送的报文写入 对应控制报文线程 的消息队列)
                    send_queue.put(MessageEncapsulationUtils.MessageToChunk(MessageEncapsulationUtils.HeartBeatMessage_pack(10)));
                    //状态转换--把状态设置为"HB_wait"
                    sur_map.put(addr, new SurvivalInformation(addr, System.currentTimeMillis(), HBstate.HB_wait, send_queue));
                } else if (state == HBstate.HB_wait && diff_time > HB_WAIT_TIME) {
                    sur_map.remove(addr);//从存活表中删除该连接
                    System.out.println("地址为"+addr+"的客户端断开");
                    //释放资源(通知那个 控制报文线程)
                }
            }
            //取出本轮while循环开始时, HBqueue内的所有元素
            //并且根据收到的 [心跳信息] 把收到心跳信息的这个连接的状态更新为"HB_sur",即"存活"(这个循环内是不考虑计时的)
            int size = HBqueue.size();
            for (int i = 0; i < size; i++) {
                HeartBeatMessage msg = HBqueue.poll();
                SurvivalInformation info = sur_map.get(msg.Addr);
                sur_map.put(msg.Addr, new SurvivalInformation(info.Addr, System.currentTimeMillis(), HBstate.HB_sur, info.send_queue));
            }
        }
    }
}


//单例的心跳队列
//需要被多个线程共享,生产者为 N个 控制报文连接的监听线程  ,消费者为HeartBeatModule
//饿汉式--程序一开始就创建了
class HeartBeatQueue {
    public ConcurrentLinkedQueue<HeartBeatMessage> queue;

    private HeartBeatQueue() {
        queue = new ConcurrentLinkedQueue<>();
    }

    private static final HeartBeatQueue INSTANCE = new HeartBeatQueue();//在类加载时就创建

    public static HeartBeatQueue getInstance() {
        return INSTANCE;
    }
}

//心跳消息
//功能: 心跳消息队列中存储的心跳消息
class HeartBeatMessage {
    String Addr;
    int SerialNumber;
    HeartBeatMessage(String a, int n){
        Addr = a;
        SerialNumber = n;
    }
}

//连接存活信息
//功能: "记录存活的客户端的Map"这个map中存储的"存活信息"
//属性: 连接地址 + 计时器
class SurvivalInformation {
    String Addr;//地址
    long start_time;//计时的起始时间
    HBstate state;//状态机的状态--心跳等待,心跳存活
    CtrlSendQueue send_queue;//此控制报文连接 的 发送队列

    SurvivalInformation(String a, long t, HBstate s, CtrlSendQueue q) {
        Addr = a;
        start_time = t;
        state = s;
        send_queue = q;
    }

    SurvivalInformation(String a, HBstate s, CtrlSendQueue q) {
        Addr = a;
        start_time = System.currentTimeMillis();
        state = s;
        send_queue = q;
    }

}

enum HBstate {
    HB_wait,
    HB_sur
}